/* 

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
   USA.
*/

#include <types.h>
#include <kerrno.h>
#include <ksynch.h>
#include <kwaitq.h>
#include <kmalloc.h>
#include <list.h>
#include <chardev.h>
#include <devices.h>
#include <vfs.h>
#include <fs/fs.h>
#include <fs/devfs.h>
#include <fd_types.h>
#include <io.h>

#define TTY_BUFFER_LEN 64
#define TTY_NAME_LEN   16

struct tty_device {
  __u32       instance;
  unsigned int      open_count;
  char              buffer[TTY_BUFFER_LEN];
  unsigned int      buffer_read;
  unsigned int      buffer_write;
  struct ksema  sem;
  struct kwaitq wq;
  int         (*write)(char c);
  unsigned int      param;
  struct tty_device *next, *prev;
};

static struct tty_device *tty_device_list;

static struct tty_device *
tty_device_find (__u32 instance)
{
  struct tty_device *t;
  int n;

  list_foreach (tty_device_list, t, n)
    {
      if (t->instance == instance)
	return t;
    }

  return NULL;
}

static int tty_read(open_file_descriptor *this,
			  __u32 dest_buf, __u32 *len)
{
  __u32 count = 0;
  struct tty_device *t;
  int ret;

   t = (struct tty_device *) this->extra_data ;

  if (*len == 0)
    return OK;

  while (count < *len)
    {
      char c;

      /* Take the semaphore */
      ksema_down (& t->sem);

      /* No keys available in the ring buffer, wait until the
	 add_chars callback wakes us up */
      if (t->buffer_read == t->buffer_write)
	{
	   ksema_up (& t->sem);
	   kwaitq_wait (& t->wq);

	  /* Go back to begining of loop: maybe someone else stole the
	     semaphore */
	  continue;
	}

      c = t->buffer[t->buffer_read];

      /* Copy the received character from the ring buffer to the
	 destination buffer */
       memcpy(dest_buf,
				(__u32) & t->buffer[t->buffer_read],
				sizeof (char));
 

      dest_buf++;

      /* Update the ring buffer read pointer */
      t->buffer_read++;
      if (t->buffer_read == TTY_BUFFER_LEN)
	t->buffer_read = 0;

      count++;

      if (t->param & IOCTLPARAM_TTY_ECHO)
	t->write (c);

      ksema_up (& t->sem);

  
    }

  *len = count;
  
  return OK;
}


static int tty_write(open_file_descriptor *this,
			   __u32 src_buf, __u32 *len)
{
  struct tty_device *t;
  char c;
  unsigned int i;
  int ret;
                   
   t = (struct tty_device *) this->extra_data ;

  for (i = 0; i < *len; i++)
    {
     memmove((__u32) & c, src_buf, sizeof(char));
     

      ksema_down (& t->sem);
      t->write (c);
      ksema_up (& t->sem);

      src_buf++;
    }

  return OK;
}


static int tty_ioctl(open_file_descriptor *this, int req_id,
			   __u32 req_arg)
{
  struct tty_device *t;

  if (req_arg != IOCTLPARAM_TTY_ECHO
      && req_arg != IOCTLPARAM_TTY_CANON)
    return -EINVAL;

  t = (struct tty_device *) this->extra_data ;

  ksema_down (& t->sem);

  switch (req_id)
    {
    case IOCTL_TTY_SETPARAM:
      t->param |= req_arg;
      break;

    case IOCTL_TTY_RESETPARAM:
      t->param &= ~req_arg;
      break;

    default:
      ksema_up (& t->sem);
      return -EINVAL;
    }

  ksema_up (& t->sem);

  return OK;
}

static int tty_open( open_file_descriptor * this,
			  void * chardev_class_custom_data)
{
  struct tty_device *t;
    
  struct fs_dev_id_t *dev_id = this->inode->dev_id;
  t = tty_device_find (dev_id->device_instance);
  if (t == NULL)
    return -ENOENT;

  ksema_down (& t->sem);
  this->extra_data = t;
  t->open_count ++;
  ksema_up (& t->sem);

  return OK;
}


static int tty_close(open_file_descriptor *this,
			   void *custom_data)
{
  struct tty_device *t;

   t = (struct tty_device *) this->extra_data ;

  ksema_down (& t->sem);
  t->open_count --;
  ksema_up (& t->sem);
  return OK;
}

void tty_add_chars (struct tty_device *t, const char *s)
{
  ksema_down (& t->sem);
  while (*s)
    {
      /* Add all characters to the ring buffer */
      t->buffer[t->buffer_write] = *s;
      t->buffer_write++;
      if (t->buffer_write == TTY_BUFFER_LEN)
	t->buffer_write = 0;
      s++;
    }
  ksema_up (& t->sem);

  /* Wake up a possibly waiting thread */
  kwaitq_wakeup (& t->wq, KWQ_WAKEUP_ALL, OK);
}

struct chardev_ops tty_ops = {
  .read    = tty_read,
  .write   = tty_write,
  .open    = tty_open,
  .close   = tty_close,
  .ioctl   = tty_ioctl
};

int tty_create (__u32 device_instance,
		      int (*write_func) (char c),
		      struct tty_device **tty_out)
{
  struct tty_device *tty;

  if (tty_device_find (device_instance) != NULL)
    return -EBUSY;

  tty = (struct tty_device *) kmalloc (sizeof(struct tty_device), 0);
  if (tty == NULL)
    return -ENOMEM;

  memset (tty->buffer, 0, sizeof(tty->buffer));
  tty->open_count   = 0;
  tty->instance     = device_instance;
  tty->write        = write_func;
  tty->buffer_read  = 0;
  tty->buffer_write = 0;
  tty->param        = IOCTLPARAM_TTY_CANON;
  kwaitq_init(& tty->wq, "tty");
  ksema_init(& tty->sem, "tty", 1);

  list_add_tail (tty_device_list, tty);

  *tty_out = tty;

  return OK;
}

int tty_remove (struct tty_device *tty)
{
  if (tty == NULL)
    return -EINVAL;

  if (OK != ksema_trydown (& tty->sem))
    return -EBUSY;

  if (tty->open_count != 0)
    return -EBUSY;

  kwaitq_dispose (& tty->wq);
  list_delete (tty_device_list, tty);

  kfree ((__u32) tty);

  return OK;
}

int tty_subsystem_setup (void)
{
  list_init (tty_device_list);
  return chardev_register_class ("tty",CHARDEV_TTY_MAJOR,
				     & tty_ops, NULL);
}

int tty_subsystem_cleanup (void)
{
  return chardev_unregister_class (CHARDEV_TTY_MAJOR);
}

